package com.bestcem.xm.member.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.ObjectId;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.domain.web.XmResultJson;
import com.bestcem.xm.common.core.sql.SQLConditionCompiler;
import com.bestcem.xm.common.core.uitls.DateUtil;
import com.bestcem.xm.common.core.uitls.EnumUtil;
import com.bestcem.xm.common.core.uitls.JsonUtil;
import com.bestcem.xm.common.core.uitls.StringUtil;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.member.constant.ColumnConstant;
import com.bestcem.xm.member.controller.dto.HttpRecordProgressDTO;
import com.bestcem.xm.member.controller.v2.param.DownloadMemberQuery;
import com.bestcem.xm.member.dao.CommonDao;
import com.bestcem.xm.member.dao.ExportRecordDao;
import com.bestcem.xm.member.dao.GroupDao;
import com.bestcem.xm.member.dao.MemberDao;
import com.bestcem.xm.member.entity.mysql.MemberEntity;
import com.bestcem.xm.member.entity.pojo.ExportRecordDO;
import com.bestcem.xm.member.entity.pojo.GroupDO;
import com.bestcem.xm.member.entity.pojo.MemberDO;
import com.bestcem.xm.member.enums.ExportStatusEnum;
import com.bestcem.xm.member.enums.MemberTypeEnum;
import com.bestcem.xm.member.enums.NewMemberStatusEnum;
import com.bestcem.xm.member.grpc.service.param.PageParam;
import com.bestcem.xm.member.grpc.service.param.export.*;
import com.bestcem.xm.member.handler.MemberImportProgressHandler;
import com.bestcem.xm.member.handler.export.ExportContext;
import com.bestcem.xm.member.handler.export.IndividualMemberExportHandler;
import com.bestcem.xm.member.handler.export.InstitionMemberExportHandler;
import com.bestcem.xm.member.handler.export.InteriorMemberExportHandler;
import com.bestcem.xm.member.service.ExportService;
import com.bestcem.xm.member.service.dto.ExportRecordDTO;
import com.bestcem.xm.member.service.dto.PageDTO;
import com.bestcem.xm.member.service.param.SelectMemberDynamicSql;
import com.bestcem.xm.member.util.AsyncUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author ming.xue <ming.xue@idiaoyan.com>
 * @desc 导出服务
 * @date 2021-07-24
 */
@Slf4j
@Service
public class ExportServiceImpl implements ExportService {
    /**
     * 通用数据访问
     */
    @Resource
    private CommonDao commonDao;

    /**
     * 联系人数据访问层
     */
    @Resource
    private MemberDao memberDao;

    /**
     * 联系人分组数据访问层
     */
    @Resource
    private GroupDao groupDao;

    /**
     * 导出记录
     */
    @Resource
    private ExportRecordDao exportRecordDao;

    @Resource
    private RedisService redisService;

    @Resource
    private IndividualMemberExportHandler individualMemberExportHandler;

    @Resource
    private InstitionMemberExportHandler institionMemberExportHandler;

    @Resource
    private InteriorMemberExportHandler interiorMemberExportHandler;

    @Resource
    private MemberImportProgressHandler memberImportProgressHandler;

    /**
     * 导出联系人
     *
     * @param param
     * @return
     */
    @Override
    public ServiceResult<String> exportMember(ExportMemberParam param) {
        ExportContext context = new ExportContext();
        context.setOrgId(param.getOrgId());
        context.setOpUserId(param.getOpUserId());
        context.setFilterMemberIds(param.getIds());
        MemberTypeEnum memberType = EnumUtil.getEnumByIndex(MemberTypeEnum.class, param.getMemberType());
        if (memberType == null) {
            memberType = MemberTypeEnum.INDIVIDUAL;
        }

        // 创建导出记录
        String id = ObjectId.next();
        initExportRecord(id, context.getOrgId(), memberType.getIndex(), context.getOpUserId(), param);
        // 异步导出数据
        asyncExportMember(context);
        return ServiceResult.success(id);
    }

    @Override
    public XmResultJson exportMember(DownloadMemberQuery query) {
        // 导出 context
        ExportContext context = new ExportContext();
        context.setOrgId(query.getOrgId());
        context.setOpUserId(query.getUserId());
        context.setFilterMemberIds(query.getFilterMemberIds());
        context.setSearch(query.getSearch());
        context.setGroupId(query.getGroupId());
        context.setSortBy(query.getSortBy());
        context.setDescending(query.getDescending());
        context.setSuperRole(query.getSuperRole());
        context.setIsSenior(query.getIsSenior());
        MemberTypeEnum memberType = EnumUtil.getEnumByIndex(MemberTypeEnum.class, query.getMType());
        if (memberType == null) {
            // 默认是 个人联系人
            memberType = MemberTypeEnum.INDIVIDUAL;
        }
        context.setMType(memberType.getIndex());
        // 设置condition 转成string 后续转为jsonobj
        if (Objects.nonNull(query.getCondition())) {
            String condition = JSON.toJSONString(query.getCondition());
            context.setCondition(condition);
        }

        // 创建导出记录
        String id = StringUtil.getId();
        context.setExportRecordId(id);
        initExportRecord(id, context.getOrgId(), memberType.getIndex(), context.getOpUserId(), query);
        // 异步导出数据
        AsyncUtil.threadPoolExecutor.execute(() -> asyncExportMember(context));
        // asyncExportMember(context);
        JSONObject object = new JSONObject();
        object.put("export_record_id", id);
        return XmResultJson.success(object);
    }

    /**
     * 异步导出数据
     *
     * @param context
     */
    public void asyncExportMember(ExportContext context) {
        if (log.isInfoEnabled()) {
            log.info("【member】开始导出...");
        }

        // 设置默认值
        // 根据修改时间 降序
        if(context.getDescending() == null){
            context.setDescending(true);
        }
        // 因为前端过来的创建/修改日期字段和数据库不一致，所以需要修改一下
        if ("createdDT".equals(context.getSortBy())) {
            context.setSortBy(ColumnConstant.Member.CREATE_TIME);
        }else if ("updatedDT".equals(context.getSortBy())) {
            context.setSortBy(ColumnConstant.Member.UPDATED_TIME);
        }else if("memberNo".equals(context.getSortBy())){
            context.setSortBy(ColumnConstant.Member.MEMBER_NO);
        }else {
            context.setSortBy(ColumnConstant.Member.UPDATED_TIME);
        }
        // 获取需要导出的联系人数据
//        List<MemberDO> members = queryMember(context.getFilterMemberIds(), context.getOrgId(),
//                context.getMType(), context.getCondition(), context.getSearch(), context.getGroupId(), context.getSortBy(), context.getDescending());
//        context.setMembers(members);
        try {
            switch (EnumUtil.getEnumByIndex(MemberTypeEnum.class, context.getMType())) {
                case INDIVIDUAL:
                    // 个人联系人导出
                    individualMemberExportHandler.exportMember(context);
                    break;
                case INSTITUTION:
                    // 机构联系人导出
                    institionMemberExportHandler.exportMember(context);
                    break;
                case INTERIOR:
                    // 内部联系人导出
                    interiorMemberExportHandler.exportMember(context);
                    break;
            }
        } catch (Exception e) {
            log.error("导出失败", e);
            // 更新导出状态为失败
            ExportRecordDO exportRecord = new ExportRecordDO();
            exportRecord.setExportId(context.getExportRecordId());
            exportRecord.setStatus(ExportStatusEnum.FAILED.getIndex());
            exportRecord.setErrorMsg(e.getMessage());
            exportRecord.setOrgId(context.getOrgId());
            exportRecordDao.updateStatusById(exportRecord);
        } finally {
            // 删除进度
            String key = "member:member.export." + context.getExportRecordId();
            redisService.del(key);
            if (log.isInfoEnabled()) {
                log.info("【member】导出完成...");
            }
        }
    }

    /**
     * 查询联系人
     *
     * @param filterMemberIds
     * @param orgId
     * @param mType
     * @param condition
     * @param search
     * @return
     */
    private List<MemberDO> queryMember(List<String> filterMemberIds, String orgId, Integer mType,
                                       String condition, String search, String groupId, String sortBy, Boolean desc) {
        SelectMemberDynamicSql dynamicSql = new SelectMemberDynamicSql();
        dynamicSql.setOrgId(orgId);
        dynamicSql.setMemberType(mType);
        dynamicSql.setStatus(NewMemberStatusEnum.MEMBER_STATUS_ACTIVATED.getIndex());
        // 精确搜索
        if(!CollectionUtils.isEmpty(filterMemberIds)){
            dynamicSql.setMemberIds(filterMemberIds);
        }

        // 分组查询
        if(!StringUtil.isNullOrEmpty(groupId)){
            GroupDO groupDO = groupDao.findById(groupId);
            if(groupDO != null){
                dynamicSql.setGroupSeq(groupDO.getSeq());
            }
        }
        dynamicSql.setOrderBy(sortBy);
        dynamicSql.setDesc(desc);

        // 模糊搜索
        if (StrUtil.isNotEmpty(search)) {
            // 去空格 + 转义正则关键字
            search = ReUtil.escape(search.trim());
            dynamicSql.setSearch(search);
        }

        // condition 解析成条件
        if (ObjectUtil.isNotEmpty(condition)) {
            ServiceResult<Wrapper<MemberEntity>> result = SQLConditionCompiler.newInstance()
                    .compile(JsonUtil.tryConvertToJSONObject(condition));
            dynamicSql.setWrapper(result.getData());
        }

        return memberDao.filterMembersByDynamicSql(dynamicSql);
    }

    /**
     * 创建导出记录
     *
     * @param id
     * @param orgId
     * @param mType
     * @param opUserId
     * @param param
     */
    private void initExportRecord(String id, String orgId, Integer mType, String opUserId, Object param) {
        ExportRecordDO exportRecord = new ExportRecordDO();
        exportRecord.setExportId(id);
        exportRecord.setOrgId(orgId);
        exportRecord.setMemberType(mType);
        exportRecord.setFileType("excel");
        exportRecord.setStatus(ExportStatusEnum.WAITING.getIndex());
        exportRecord.setParams(JSON.toJSONString(param));
        exportRecord.setOpUserId(opUserId);
        exportRecord.setCreateTime(DateUtil.getCommonDate());
        exportRecord.setUpdateTime(exportRecord.getCreateTime());
        exportRecordDao.saveRecord(exportRecord);
    }

    @Override
    public ServiceResult<PageDTO<ExportRecordDTO>> listExportRecords(ListExportRecordsParam param) {
        // 总条数
        Long count = exportRecordDao.countByUser(param.getOrgId(), param.getOpUserId(), param.getMemberTypeEnum());

        // 数量校验
        PageParam pageParam = param.getPageParam();
        long skip = pageParam.getSize() * pageParam.getPage();
        if (count < 1 || count < skip) {
            return ServiceResult.success(new PageDTO<ExportRecordDTO>(new ArrayList<>(0), count.intValue()));
        }

        // 查询记录列表
        List<ExportRecordDO> exportRecords = exportRecordDao.listByUser(param.getOrgId(), param.getOpUserId(), param.getMemberTypeEnum(), skip, pageParam.getSize());

        // 类型转化
        List<ExportRecordDTO> exportRecordDTOS = exportRecords.stream().map(this::convertFrom).collect(Collectors.toList());

        return ServiceResult.success(new PageDTO<ExportRecordDTO>(exportRecordDTOS, count.intValue()));
    }

    @Override
    public ServiceResult<ExportRecordDTO> getExportRecord(GetExportRecordParam param) {
        // 查询记录列表
        ExportRecordDO exportRecord = exportRecordDao.findById(param.getOrgId(), param.getId());

        if (exportRecord == null) {
            log.error("导出记录不存在:{}", param);
            return ServiceResult.fail("导出记录不存在");
        }

        // 类型转化
        ExportRecordDTO exportRecordDTO = this.convertFrom(exportRecord);

        return ServiceResult.success(exportRecordDTO);
    }

    @Override
    public ServiceResult<Integer> batchDeleteExportRecords(BatchDeleteExportRecordsParam param) {
        Integer deleteCount = exportRecordDao.batchDeleteByIds(param.getOrgId(), param.getIds());
        if (deleteCount == 0) {
            return ServiceResult.fail("删除失败");
        }
        return ServiceResult.success(deleteCount);
    }

    @Override
    public ServiceResult<Float> getExportProgress(GetExportProgressParam param) {
        // 获取导入进度状态
        String statusKey = "member:member.export." + param.getId();

        String redisStatus = "";
        if (org.springframework.util.StringUtils.hasText(redisStatus)) {
            // 缓存状态 redisStatus = {"complete_count": complete_count, "total_count": total_count, "last_timestamp":上次更新状态时的时间戳}

            // 已导出数
            float completeCount = 1;
            // 总导出数
            float totalCount = 1;
            // 最后导出状态更新时间
            LocalDateTime lastUpdateTime = LocalDateTime.now();

            // 状态更新时间超过3600s 更新DB导出状态 return 1.0
            // else if count == 0 return 1.0
            // else return complete_count / total_count
            if (LocalDateTime.now().minusSeconds(3600L).isAfter(lastUpdateTime) || totalCount == 0) {
                // 更新导出状态超时
                return ServiceResult.success(1F);
            }

            return ServiceResult.success(completeCount / totalCount);
        }

        // 缓存状态不存在
        // 查询DB导出记录状态 success 1.0 else 0.0
        ExportRecordDO exportRecord = exportRecordDao.findById(param.getOrgId(), param.getId());
        // 有点问题
        if (exportRecord == null || exportRecord.getStatus().equals(1)) {
            return ServiceResult.fail("导出记录不存在");
        }
        // 成功或者失败 return 1.0 枚举替换
        if (ExportStatusEnum.SUCCESS.matches(exportRecord.getStatus()) || ExportStatusEnum.FAILED.matches(exportRecord.getStatus())) {
            return ServiceResult.success(1F);
        }

        // 校验是否正在导入
//        MEMBER_TYPE_KEY_MAP = {
//                model_enums.MEMBER_MTYPE_INDIVIDUAL: "individual",
//                model_enums.MEMBER_MTYPE_INSTITUTION: "institution",
//                model_enums.MEMBER_MTYPE_INTERIOR: "interior",
//    }
        // export "member.memberNo.{org_id}.{MEMBER_TYPE_KEY_MAP[mtype]}"
        boolean isExporting = redisService.hasKey("");
        if (isExporting) {
            return ServiceResult.success(0F);
        }
        // if 存在已有的任务 return 0.0  1.0
        return ServiceResult.success(1F);
    }

    @Override
    public ServiceResult<HttpRecordProgressDTO> getExportProgress(List<String> id, String orgId) {
        HttpRecordProgressDTO dto = new HttpRecordProgressDTO();
        if (CollUtil.isNotEmpty(id)) {
            List<Double> percents = new ArrayList<>();
            id.forEach(item -> {
                Double progress = memberImportProgressHandler.getProgress(item, 1, null, orgId);
                percents.add(progress);
            });
            dto.setPercent(percents);
        }
        return ServiceResult.success(dto);
    }

    private ExportRecordDTO convertFrom(ExportRecordDO exportRecord) {
        ExportRecordDTO exportRecordDTO = new ExportRecordDTO();
        exportRecordDTO.setId(exportRecord.getExportId());
        exportRecordDTO.setCreateTime(exportRecord.getCreateTime());
        exportRecordDTO.setErrorMsg(exportRecord.getErrorMsg());
        exportRecordDTO.setExportStatus(exportRecord.getStatus());
        exportRecordDTO.setFileName(exportRecord.getFileName());
        exportRecordDTO.setFileSize(exportRecord.getFileSize());
        exportRecordDTO.setFileType(exportRecord.getFileType());
        exportRecordDTO.setFileUrl(exportRecord.getFileUrl());
        exportRecordDTO.setMemberType(exportRecord.getMemberType());
        exportRecordDTO.setOpUserId(exportRecord.getOpUserId());
        exportRecordDTO.setOrgId(exportRecord.getOrgId());
        exportRecordDTO.setParams(exportRecord.getParams());
        exportRecordDTO.setUpdateTime(exportRecord.getUpdateTime());
        return exportRecordDTO;
    }
}
